/*
 * ﻿Copyright (C) 2009-2013 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.gwetons.client.timer;

import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.newmainsoftech.gwetons.logging.client.JsniLoggerWrapAdaptedGwtTestCase;
import com.newmainsoftech.gwetons.shared.logging.LoggerWrap;
import com.newmainsoftech.gwetons.shared.timer.TimerController;
import com.newmainsoftech.gwetons.shared.timer.TimerEventListener;

public class GwtTest_TimerControllerImpl extends JsniLoggerWrapAdaptedGwtTestCase {
	
	@Override
	public String getModuleName() {
	    return "com.newmainsoftech.gwetons.GWeTons";
	}

	@Override
	protected String getNameForLogging() {
		return this.getClass().getName();
	}
	
	TimerControllerImpl timerControllerImpl;
	
	LoggerWrap loggerWrap;
	
	@Override
	public void gwtSetUp() {
		timerControllerImpl = new TimerControllerImpl();
		
		if ( loggerWrap == null) {
			getLoggerInitializerForTest().setupLogger();
			
			loggerWrap = getJsniLoggerWrap().getLoggerWrap();
				loggerWrap.getLogger().setLevel( Level.ALL);
				
			Logger otherLogger = Logger.getLogger( timerControllerImpl.getNameForLogging());
				otherLogger.setLevel( Level.ALL);
		}
	}
	
	// fields to manage execution of test using delayTestFinish method to run one test at time in sequence
	private static int summedDelayForRunningTest = 0;
	private static long delayAddtionTime = (new Date()).getTime(); 
		static int getSummedDelayForRunningTest() {
			long currentTime = (new Date()).getTime();
			if ( currentTime > (delayAddtionTime + summedDelayForRunningTest)) {
				summedDelayForRunningTest = 0;
				delayAddtionTime = currentTime;
			}
			return summedDelayForRunningTest;
		}
		static void addToSummedDelayForRunningTest( int delay) {
			long currentTime = (new Date()).getTime();
			if ( currentTime > (delayAddtionTime + summedDelayForRunningTest)) {
				summedDelayForRunningTest = delay;
				delayAddtionTime = currentTime;
			}
			else {
				summedDelayForRunningTest = summedDelayForRunningTest + delay;
			}
		}
		static void subtractFromSummedDelayForRunningTest( int delay) {
			summedDelayForRunningTest = summedDelayForRunningTest - delay;
			if ( summedDelayForRunningTest < 0) {
				summedDelayForRunningTest = 0;
				delayAddtionTime = (new Date()).getTime();
			}
		}
	// --------------------------------------------------------------------------------------------
	
	interface EventConfirmInterface {
		boolean isEventConsumed();
		void consumeEvent();
	}
	
	// test for schedule method -------------------------------------------------------------------
	class EventForScheduleTest implements TimerEventListener, EventConfirmInterface {
		private String methodName;
		private int interval;
		private int testFinishDelay;
		public EventForScheduleTest( String methodName, int interval, int testFinishDelay) {
			this.methodName = methodName;
			this.interval = interval;
			this.testFinishDelay = testFinishDelay;
		}
		
		private boolean isEventConsumed = false;
			public boolean isEventConsumed() {
				return isEventConsumed;
			}
			public void consumeEvent() {
				isEventConsumed = true;
				GwtTest_TimerControllerImpl.subtractFromSummedDelayForRunningTest( testFinishDelay);
			}
		private long scheduledTime = 0;
			void setScheduledTime( long scheduledTime) {
				this.scheduledTime = scheduledTime;
			}
			
		public void fire() {
			if ( !isEventConsumed()) {
				consumeEvent();
				
				Date endDate = new Date();
					loggerWrap.log( 
							Level.FINE, 
							"Test event for " + methodName + " is fired at " + endDate.toString()
							);
				
				int elapsedTime = Long.valueOf( endDate.getTime() - scheduledTime).intValue();
					if ( elapsedTime < interval) {
						String message 
						= "Scheduled test event was executed before interval " + interval + "[ms] past. " 
								+ "Elapsed time: " + elapsedTime + "[ms]";
						fail( message);
						loggerWrap.log( Level.SEVERE, methodName + " test failed; " + message);
					}
					
				finishTest();
				loggerWrap.exiting( methodName);
			}
		}
	}
	
	public void test_schedule() {
		String methodName = "testSchedule test";
		loggerWrap.entering( methodName, null);
	  
		int interval = 5000; // 5sec
		int delay = GwtTest_TimerControllerImpl.getSummedDelayForRunningTest() + interval;
		int testFinishDelay = delay + interval;
		EventForScheduleTest eventForScheduleTest 
		= new EventForScheduleTest( methodName, interval, testFinishDelay);
		
		int summedDelayForRunningTest = GwtTest_TimerControllerImpl.getSummedDelayForRunningTest();
		Date date = new Date( (new Date()).getTime() + summedDelayForRunningTest + interval);
		loggerWrap.log( 
				Level.FINE, 
				"Scheduling a test event for " + methodName 
				+ " to be exectued at about " + date.toString()
				);
			
		eventForScheduleTest.setScheduledTime( (new Date()).getTime());
		
		timerControllerImpl.schedule( 
				eventForScheduleTest,
				GwtTest_TimerControllerImpl.getSummedDelayForRunningTest() + interval);
				// Intentionally don't use delay variable here since 
				// TimerControllerImplTest.getSummedDelayForRunningTest() may return different updated value here
		
		GwtTest_TimerControllerImpl.addToSummedDelayForRunningTest( testFinishDelay);
		delayTestFinish( testFinishDelay);
	}
	// --------------------------------------------------------------------------------------------
	
	// test for scheduleRepeating method ----------------------------------------------------------
	class EventForRecurrentScheduleTest implements TimerEventListener, EventConfirmInterface {
		private String methodName;
		private int interval;
		private int repeatCount;
		private int testPeriod;
		public EventForRecurrentScheduleTest( 
				String methodName, int interval, int repeatCount, int testPeriod) {
			this.methodName = methodName;
			this.interval = interval;
			this.repeatCount = repeatCount;
			this.testPeriod = testPeriod;
		}
		
		private Date startDate;
			Date getStartDate() {
				return startDate;
			}
			void setStartDate( Date startDate) {
				this.startDate = startDate;
			}

		private boolean isEventConsumed = false;
			public boolean isEventConsumed() {
				return isEventConsumed;
			}
			public void consumeEvent() {
				isEventConsumed = true;
				GwtTest_TimerControllerImpl.subtractFromSummedDelayForRunningTest( testPeriod);
			}
			
		private int counter = 0;
		
		public void fire() {
			if ( !isEventConsumed()) {
				loggerWrap.log( 
						Level.FINE, 
						"Recurrent event for " + methodName + " is executed " + (++counter) + " time(s)."
						);
				
				Date endDate = new Date();
				int elapsedTime = Long.valueOf( endDate.getTime() - startDate.getTime()).intValue();
					if ( elapsedTime < interval) {
						String message 
						= "Recurrent event is executed before the interval " + interval 
								+ "[ms] elapses since previous one. Elapsed time: " + elapsedTime 
								+ "[ms]";
						fail( message);
						consumeEvent();
						loggerWrap.log( Level.SEVERE, methodName + " test failed; " + message);
						loggerWrap.exiting( methodName);
					}
					
				startDate = endDate;
				
				if ( counter >= repeatCount) {
					consumeEvent();
					finishTest();
					loggerWrap.exiting( methodName);
				}
			}
		}
	}
	
	public void test_scheduleRepeating() {
		String methodName = getName() + " test";
		loggerWrap.entering( methodName, null);
		  
		int interval = 4500; // 4sec
		int repeatCount = 3;
		int delay = GwtTest_TimerControllerImpl.getSummedDelayForRunningTest() + interval;
		int testPeriod = repeatCount * interval + 2 * interval; // 20sec
			// 2 * interval is for delay to wait for previous test completion and buffer for completion of this test

		EventForRecurrentScheduleTest eventForRecurrentScheduleTest
		= new EventForRecurrentScheduleTest( methodName, interval, repeatCount, testPeriod);
		
		Date date = new Date( (new Date()).getTime() + delay);
		loggerWrap.log( 
				Level.FINE, 
				"Scheduling the first one of recurrent events for " + methodName 
				+ " to be exectued at about " + date.toString()
				);
		
		eventForRecurrentScheduleTest.setStartDate( new Date());
		
		timerControllerImpl.scheduleRepeating( eventForRecurrentScheduleTest, delay);
		
		loggerWrap.log(
				Level.FINEST, 
				"Delaying finishing %1$s for %2$s[msec]."
				.replace( "%1$s", methodName)
				.replace( "%2$s", String.valueOf( delay))
				);
		GwtTest_TimerControllerImpl.addToSummedDelayForRunningTest( testPeriod);
		delayTestFinish( testPeriod);
	}
	// --------------------------------------------------------------------------------------------
	
	// test cancel method -------------------------------------------------------------------------
	class FailTestStub implements TimerEventListener, EventConfirmInterface {
		String methodName;
		private int testPeriod;
		public FailTestStub( String methodName, int testPeriod) {
			this.methodName = methodName;
			this.testPeriod = testPeriod;
		}
		
		private EventConfirmInterface subjectFinishJob;
			void setSubjectFinishJob(EventConfirmInterface subjectFinishJob) {
				this.subjectFinishJob = subjectFinishJob;
			}

		private boolean isEventConsumed = false;
			public boolean isEventConsumed() {
				return isEventConsumed;
			}
			public void consumeEvent() {
				isEventConsumed = true;
			}
		
		public void fire() {
			if ( !isEventConsumed()) {
				subjectFinishJob.consumeEvent();
				consumeEvent();
				GwtTest_TimerControllerImpl.subtractFromSummedDelayForRunningTest( testPeriod);
				String message 
				= "FailTestStub event has not been caceled but fired at " + ( new Date()).toString();
				fail( message);
				loggerWrap.log( Level.SEVERE, methodName + " test failed; " + message);
				loggerWrap.exiting( methodName);
			}
		}
	}
	
	class CancelFailTestStub implements TimerEventListener, EventConfirmInterface {
		private boolean isEventConsumed = false;
			public boolean isEventConsumed() {
				return isEventConsumed;
			}
			public void consumeEvent() {
				isEventConsumed = true;
			}
	
		private TimerController timerController;
		private String methodName;
		private FailTestStub failTestStub;
		
		public CancelFailTestStub( TimerController timerController, String methodName, FailTestStub failTestStub) {
			this.timerController = timerController;
			this.methodName = methodName;
			this.failTestStub = failTestStub;
		}

		public void fire() {
			if ( !isEventConsumed()) {
				consumeEvent();
				
				loggerWrap.log( 
						Level.FINE, 
						"Caceling FailTestStub event for " + methodName + " at " + (new Date()).toString()
						);
				
				timerController.cancel( failTestStub);
			}
		}
	}
	class TerminateCancelTest implements TimerEventListener, EventConfirmInterface {
		private String methodName;
		private int testPeriod;
		public TerminateCancelTest( String methodName, int testPeriod) {
			this.methodName = methodName;
			this.testPeriod = testPeriod;
		}
		
		private EventConfirmInterface failTestStub;
			void setFailTestStub(EventConfirmInterface failTestStub) {
				this.failTestStub = failTestStub;
			}

		boolean isEventConsumed;
			public boolean isEventConsumed() {
				return isEventConsumed;
			}
	
			public void consumeEvent() {
				isEventConsumed = true;
			}

		public void fire() {
			if ( !isEventConsumed()) {
				failTestStub.consumeEvent();
				consumeEvent();
				
				GwtTest_TimerControllerImpl.subtractFromSummedDelayForRunningTest( testPeriod);
				finishTest();
				loggerWrap.exiting( methodName);
			}
		}
	}
	
	public void test_cancel() {
		String methodName = "testCancel test";
		loggerWrap.entering( methodName, null);
	  
		int interval = 2000; // 2sec
		int testPeriod = interval * 4 + interval; // 10sec
		
		FailTestStub failTestStub = new FailTestStub( methodName, testPeriod);
		CancelFailTestStub cancelFailTestStub 
		= new CancelFailTestStub( timerControllerImpl, methodName, failTestStub);
		TerminateCancelTest terminateCancelTest = new TerminateCancelTest( methodName, testPeriod);
			failTestStub.setSubjectFinishJob( terminateCancelTest);
			terminateCancelTest.setFailTestStub( failTestStub);
			
		int delay = GwtTest_TimerControllerImpl.getSummedDelayForRunningTest() + interval;
		Date date = new Date( (new Date()).getTime() + delay + interval);
		loggerWrap.log( 
				Level.FINE, 
				"Scheduling FailTestStub event for " + methodName 
				+ " to be exectued at about " + date.toString()
				);
		timerControllerImpl.schedule( failTestStub, (delay + interval));
		timerControllerImpl.schedule( cancelFailTestStub, delay);
		timerControllerImpl.schedule( terminateCancelTest, (delay + 2 * interval));
		
		GwtTest_TimerControllerImpl.addToSummedDelayForRunningTest( testPeriod);
		
		delayTestFinish( testPeriod);
	}
	// --------------------------------------------------------------------------------------------
	// test for cancelAll method ------------------------------------------------------------------
	class CancelAllTestTerminationEvent implements TimerEventListener, EventConfirmInterface {
		private String methodName;
		public CancelAllTestTerminationEvent( String methodName) {
			this.methodName = methodName;
		}
		private boolean isEventConsumed = false;
			public boolean isEventConsumed() {
				return isEventConsumed;
			}
			public void consumeEvent() {
				isEventConsumed = true;
			}
		public void fire() {
			if ( !isEventConsumed()) {
				consumeEvent();
				
				loggerWrap.log( 
						Level.FINE, 
						"Test termination event for " + methodName 
						+ " is fired at " + (new Date()).toString()
						);
				finishTest();
				loggerWrap.exiting( methodName);
			}
		}
	}
	class CancelAllFailTestStubEvent implements TimerEventListener, EventConfirmInterface {
		private TimerController timerController;
		private String methodName;
		private int delay;
		private CancelAllTestTerminationEvent cancelAllTestTerminationEvent;
			public CancelAllTestTerminationEvent getCancelAllTestTerminationEvent() {
				return cancelAllTestTerminationEvent;
			}
		public CancelAllFailTestStubEvent( 
				TimerController timerController, String methodName, int delay) {
			this.timerController = timerController;
			this.methodName = methodName;
			this.delay = delay;
			cancelAllTestTerminationEvent = new CancelAllTestTerminationEvent( methodName);
		}
		
		private boolean isEventConsumed = false;
			public boolean isEventConsumed() {
				return isEventConsumed;
			}
			public void consumeEvent() {
				isEventConsumed = true;
			}
		
		public void fire() {
			if ( !isEventConsumed()) {
				consumeEvent();
				
				loggerWrap.log( 
						Level.FINE, 
						"Cancelling all scheudled FailTestStub events for " 
						+ methodName + " at " + (new Date()).toString()
						);
				timerController.cancelAll();
				
				Date terminateDate = new Date( (new Date()).getTime() + delay);
				loggerWrap.log( 
						Level.FINE, 
						"Scheduling termination job for " + methodName 
						+ " to be executed at about " + terminateDate.toString()
						);
				
				timerController.schedule( cancelAllTestTerminationEvent, delay);
			}
		}
	}
	
	public void test_cancelAll() {
		String methodName = getName() + " test";
		loggerWrap.entering( methodName, null);
	  
		int interval = 1500; // 1.5sec
		CancelAllFailTestStubEvent cancelAllFailTestStubEvent 
		= new CancelAllFailTestStubEvent( timerControllerImpl, methodName, interval); 
		CancelAllTestTerminationEvent cancelAllTestTerminationEvent 
		= cancelAllFailTestStubEvent.getCancelAllTestTerminationEvent();
		
		int failTestStubCount = 4;
		int summedDelayForRunningTest 
		= GwtTest_TimerControllerImpl.getSummedDelayForRunningTest();
		
		loggerWrap.log( 
				Level.FINE, 
				"Scheduling multiple FailTestStub events for " + methodName 
				+ " at " + (new Date()).toString()
				);
		int delay = 0;
		for( int testStubIndex = 1; testStubIndex <= failTestStubCount; testStubIndex++) {
			FailTestStub failTestStub = new FailTestStub( methodName, 0);
				failTestStub.setSubjectFinishJob( cancelAllTestTerminationEvent);
			delay = summedDelayForRunningTest + interval + interval * testStubIndex;
			timerControllerImpl.schedule( failTestStub, delay);
		} // for
			loggerWrap.log( 
					Level.FINE, 
					"The last FailTestStub event for " + methodName + " is scheduled to " 
					+ "be executed at about " + (new Date( (new Date()).getTime() + delay)).toString()
					);
		
		timerControllerImpl.schedule( 
				cancelAllFailTestStubEvent, (summedDelayForRunningTest + interval));
		
		int testPeriod = delay - summedDelayForRunningTest + 2 * interval; 
		
		loggerWrap.log(
				Level.FINEST, 
				"Delaying finishing %1$s for %2$s[msec]."
				.replace( "%1$s", methodName)
				.replace( "%2$s", String.valueOf( delay))
				);
		GwtTest_TimerControllerImpl.addToSummedDelayForRunningTest( testPeriod);
		delayTestFinish( testPeriod);
	}
	// --------------------------------------------------------------------------------------------
}
